/* 检测数据类型 */
const class2type = {},
  toString = class2type.toString,
  hasOwn = class2type.hasOwnProperty
const toType = function toType(obj) {
  let reg = /^\[object ([\w\W]+)\]$/
  if (obj == null) return obj + ''
  return typeof obj === 'object' || typeof obj === 'function'
    ? reg.exec(toString.call(obj))[1].toLowerCase()
    : typeof obj
}
const isFunction = function isFunction(obj) {
  return typeof obj === 'function' && typeof obj.nodeType !== 'number' && typeof obj.item !== 'function'
}
const isWindow = function isWindow(obj) {
  return obj != null && obj === obj.window
}
const isArrayLike = function isArrayLike(obj) {
  let length = !!obj && 'length' in obj && obj.length,
    type = toType(obj)
  if (isFunction(obj) || isWindow(obj)) return false
  return type === 'array' || length === 0 || (typeof length === 'number' && length > 0 && length - 1 in obj)
}
const isPlainObject = function isPlainObject(obj) {
  let proto, Ctor
  if (!obj || toString.call(obj) !== '[object Object]') return false
  proto = Object.getPrototypeOf(obj)
  if (!proto) return true
  Ctor = hasOwn.call(proto, 'constructor') && proto.constructor
  return typeof Ctor === 'function' && Ctor === Object
}
const isEmptyObject = function isEmptyObject(obj) {
  let keys = Object.getOwnPropertyNames(obj)
  if (typeof Symbol !== 'undefined') keys = keys.concat(Object.getOwnPropertySymbols(obj))
  return keys.length === 0
}
const isNumeric = function isNumeric(obj) {
  var type = toType(obj)
  return (type === 'number' || type === 'string') && !isNaN(obj - parseFloat(obj))
}

/* 函数的防抖和节流 */
const clearTimer = function clearTimer(timer) {
  if (timer) clearTimeout(timer)
  return null
}
const debounce = function debounce(func, wait, immediate) {
  if (typeof func !== 'function') throw new TypeError('func is not a function!')
  if (typeof wait === 'boolean') {
    immediate = wait
    wait = undefined
  }
  wait = +wait
  if (isNaN(wait)) wait = 300
  if (typeof immediate !== 'boolean') immediate = false
  let timer = null
  return function operate(...params) {
    let now = !timer && immediate
    timer = clearTimer(timer)
    timer = setTimeout(() => {
      if (!immediate) func.call(this, ...params)
      timer = clearTimer(timer)
    }, wait)
    if (now) func.call(this, ...params)
  }
}
const throttle = function throttle(func, wait) {
  if (typeof func !== 'function') throw new TypeError('func is not a function!')
  wait = +wait
  if (isNaN(wait)) wait = 300
  let timer = null,
    previous = 0
  return function operate(...params) {
    let now = +new Date(),
      remaining = wait - (now - previous)
    if (remaining <= 0) {
      func.call(this, ...params)
      previous = +new Date()
      timer = clearTimer(timer)
    } else if (!timer) {
      timer = setTimeout(() => {
        func.call(this, ...params)
        previous = +new Date()
        timer = clearTimer(timer)
      }, remaining)
    }
  }
}

/* 数组和对象的操作 */
const mergeArray = function mergeArray(first, second) {
  if (typeof first === 'string') first = Object(first)
  if (typeof second === 'string') second = Object(second)
  if (!isArrayLike(first)) first = []
  if (!isArrayLike(second)) second = []
  let len = +second.length,
    j = 0,
    i = first.length
  for (; j < len; j++) {
    first[i++] = second[j]
  }
  first.length = i
  return first
}
const each = function each(obj, callback) {
  let isArray = isArrayLike(obj),
    isObject = isPlainObject(obj)
  if (!isArray && !isObject) throw new TypeError('obj must be a array or likeArray or plainObject')
  if (!isFunction(callback)) throw new TypeError('callback is not a function')
  if (isArray) {
    for (let i = 0; i < obj.length; i++) {
      let item = obj[i],
        index = i
      if (callback.call(item, item, index) === false) break
    }
    return obj
  }
  let keys = Object.getOwnPropertyNames(obj)
  if (typeof Symbol !== 'undefined') keys = keys.concat(Object.getOwnPropertySymbols(obj))
  for (let i = 0; i < keys.length; i++) {
    let key = keys[i],
      value = obj[key]
    if (callback.call(value, value, key) === false) break
  }
  return obj
}
const merge = function merge(...params) {
  let options,
    target = params[0],
    i = 1,
    length = params.length,
    deep = false,
    treated = params[length - 1]
  toType(treated) === 'set' ? length-- : (treated = new Set())
  if (typeof target === 'boolean') {
    deep = target
    target = params[i]
    i++
  }
  if (target == null || (typeof target !== 'object' && !isFunction(target))) target = {}
  for (; i < length; i++) {
    options = params[i]
    if (options == null) continue
    if (treated.has(options)) return options
    treated.add(options)
    each(options, (copy, name) => {
      let copyIsArray = Array.isArray(copy),
        copyIsObject = isPlainObject(copy),
        src = target[name]
      if (deep && copy && (copyIsArray || copyIsObject)) {
        if (copyIsArray && !Array.isArray(src)) src = []
        if (copyIsObject && !isPlainObject(src)) src = {}
        target[name] = merge(deep, src, copy, treated)
      } else if (copy !== undefined) {
        target[name] = copy
      }
    })
  }
  return target
}
const clone = function clone(...params) {
  let target = params[0],
    deep = false,
    length = params.length,
    i = 1,
    isArray,
    isObject,
    result,
    treated
  if (typeof target === 'boolean' && length > 1) {
    deep = target
    target = params[1]
    i = 2
  }
  treated = params[i]
  if (!treated) treated = new Set()
  if (treated.has(target)) return target
  treated.add(target)
  isArray = Array.isArray(target)
  isObject = isPlainObject(target)
  if (target == null) return target
  if (!isArray && !isObject && !isFunction(target) && typeof target === 'object') {
    try {
      return new target.constructor(target)
    } catch (_) {
      return target
    }
  }
  if (!isArray && !isObject) return target
  result = new target.constructor()
  each(target, (copy, name) => {
    if (deep) {
      result[name] = clone(deep, copy, treated)
      return
    }
    result[name] = copy
  })
  return result
}

/* 设定具备有效期的localStorage存储方案 */
const storage = {
  set(key, value) {
    localStorage.setItem(
      key,
      JSON.stringify({
        time: +new Date(),
        value
      })
    )
  },
  get(key, cycle = 2592000000) {
    cycle = +cycle
    if (isNaN(cycle)) cycle = 2592000000
    let data = localStorage.getItem(key)
    if (!data) return null
    let { time, value } = JSON.parse(data)
    if (+new Date() - time > cycle) {
      storage.remove(key)
      return null
    }
    return value
  },
  remove(key) {
    localStorage.removeItem(key)
  }
}

/* 日期格式化 */
const formatTime = function formatTime(time, template) {
  if (typeof time !== 'string') {
    time = new Date().toLocaleString('zh-CN', { hour12: false })
  }
  if (typeof template !== 'string') {
    template = '{0}年{1}月{2}日 {3}:{4}:{5}'
  }
  let arr = []
  if (/^\d{8}$/.test(time)) {
    let [, $1, $2, $3] = /^(\d{4})(\d{2})(\d{2})$/.exec(time)
    arr.push($1, $2, $3)
  } else {
    arr = time.match(/\d+/g)
  }
  return template.replace(/\{(\d+)\}/g, (_, $1) => {
    let item = arr[$1] || '00'
    if (item.length < 2) item = '0' + item
    return item
  })
}

const utils = {
  toType,
  isFunction,
  isWindow,
  isArrayLike,
  isPlainObject,
  isEmptyObject,
  isNumeric,
  debounce,
  throttle,
  mergeArray,
  each,
  merge,
  clone,
  storage,
  formatTime
}

/* 处理冲突 */
if (typeof window !== 'undefined') {
  let $ = window._
  utils.noConflict = function noConflict() {
    if (window._ === utils) {
      window._ = $
    }
    return utils
  }
}

/* 导出API */
export default utils
